// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

/* WW3DText.m
 * Fashioned by Ian Wilkinson.
 * Inception was Sun Jul 17 16:09:45 BST 1994.
 *
 * My History:
 * Hacked by wave on Aug 20.
 * Hacked more by wave on Aug 25 to add archiving.
 * Hacked just a bit more by wave on Aug 25 to fix storage problems
 *     (noted by Ian).
 * Hacked by iw on Sept 6 to provide correct polygon order
 *     for RiGeneralPolygon() (see orderCounters.)
 * Hacked by wave on Sept 12 to add justification; bumped version number to 2
 * Hacked by wave on Sept 13 to add count and objectAt: methods
 * Hacked by wave in early Feb to add conformance to additions to WWRenderable protocol and 
 *     and some nil and NULL setting after frees.       
 * Wildly-controlled engineering by iw on 19 Mar 95 to introduce RiArchiveRecords into
 *     the RIB stream.
 */

/* bugs: - loses its shit when handed really complicated characters;
 *         like "WavesWorld" in OutwestHalfFull from Emigre...
 *       - it's polygons, not a trimmed NURBS (although qrman wouldn't actually image it 
 *         right, so...) (probably the correct thing to do is have two reps; this one to
 *         spew at qrman, a trimmed NURBS to spew at prman)
 *       - due to the arbitariness of fonts, this object may be in way over its head
 *         for a given piece of text.  Should do some sort of sanity check...
 *         Not sure where and when to bail, though.  any ideas, ian?
 *       - I can still get it to crash after a while, even using pretty sane stuff,
 *         i.e., "aAbBcC" in Helvetica, a bunch of times.  It usually dies in 
 *         [Storage insertElement:at:]...  any ideas, ian?
 */

#import "WW3DText.h"

#import "RIBArchiveRecord.h"
#import "RIBGeneralPolygon.h"
#import "WW3DTextWraps.h"
#import "RIBCommandList.h"
#import "RIBTransformBegin.h"
#import "RIBTransformEnd.h"
#import "RIBTranslate.h"
#import "RIBScale.h"
#import "WWSample.h"
#import "WW3DAttributeState.h"

// for WW_POINT definition...
#import "WWEveParser.h"

// for isspace()
#import <ctype.h>

typedef struct {
    int numLoops;
    int numCounters;
    int numCntlPts;
} WW3DTextMetrics;

@interface WW3DText(Private)
- getCharPath;
- copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer;
- orderCounters;
- (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx;
- (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops;
- readCharPath:(const char *)theText usingFont:(Font *)theFont;
- generateRIBCommandList;
@end

@implementation WW3DText(Private)
static const char *FontIsProtected = "%s is protected, so its outline is entirely private.";
static const int PadComment = 36;	/* 'c' (of ``string'') in fontName fontSize point */

static BOOL IsCounter(float x0, float y0, float x1, float y1, float x2, float y2)
{
    return ((x2 - x0)*(y1 - y0) - (x1 - x0)*(y2 - y0) < 0) ? YES : NO;
}

- getCharPath
{
    float *charPath;
    int i, len, outlineLen = 0, isProtected, nLoopsInPath, *theLoops;

    
    if (!currentText || !currentFont) return self;
    PSWWW3DTextIsFontProtected([currentFont name], [currentFont pointSize], "l", &isProtected);
    if (isProtected) {
	NXRunAlertPanel([NXApp appName], FontIsProtected, NULL, NULL, NULL, [currentFont displayName]);
	return self;
    }
    nLoopsInPath = 0;
    len = strlen(currentText);
    [[textMetrics empty] setNumSlots:len];
    for (i = 0; i < len; i++) {
	WW3DTextMetrics metrics;
	int nLoops, nCntlPts;

	PSWWW3DTextMetrics([currentFont name], [currentFont pointSize], &currentText[i], &nLoops, &nCntlPts);
	metrics.numLoops = nLoops;
	metrics.numCntlPts = nCntlPts;
	[textMetrics insertElement:(void *)&metrics at:i];
	outlineLen += nCntlPts;
	nLoopsInPath += nLoops;
    }
    theLoops = (int *)NXZoneMalloc([self zone], nLoopsInPath*sizeof(int));
    if (!theLoops) return self;
    PSWWW3DTextLoops([currentFont name], [currentFont pointSize], currentText, nLoopsInPath, theLoops);
    charPath = (float *)NXZoneMalloc([self zone], outlineLen*sizeof(float));
    if (!charPath) {
	NXZoneFree([self zone], theLoops);
        theLoops = NULL;
	return self;
    }
    PSWWW3DTextOutline([currentFont name], [currentFont pointSize], currentText, outlineLen, charPath);
    [[outline empty] setNumSlots:outlineLen];
    for (i = 0; i < outlineLen; i++) {
	[outline insertElement:(void *)&charPath[i] at:i];
    }
    [[loops empty] setNumSlots:nLoopsInPath];
    for (i = 0; i < nLoopsInPath; i++) {
	[loops insertElement:(void *)&theLoops[i] at:i];
    }
    NXZoneFree([self zone], charPath);
    charPath = NULL;
    NXZoneFree([self zone], theLoops);
    theLoops = NULL;
    return self;
}

- copyControlPoints:(int)nCntlPts startingAt:(int)outlineIdx to:(Storage *)outlineBuffer
{
    int i;
    
    for (i = outlineIdx; i < outlineIdx+nCntlPts; i++) {
	[outlineBuffer addElement:[outline elementAt:i]];
    }
    return self;
}

- orderCounters
/*
 * The order of the counters from charpath and pathforall may
 * not be suitable for RiGeneralPolygon(). Here, we ensure
 * any boundary for a character appears before the holes (if any).
 * And yes, this solution is ugly...
 */
{
    Storage *outlineStack, *loopStack, *outlineBuffer, *loopBuffer;
    int loopIdx = 0, outlineIdx = 0;
    int len, i, j, k, nLoops, nCntlPts;
    
    if (!currentText) return self;
    outlineStack = [[Storage alloc] initCount:0
				    elementSize:sizeof(int)
				    description:"i"];
    loopStack = [[Storage alloc] initCount:0
				 elementSize:sizeof(int)
				 description:"i"];
    outlineBuffer = [[Storage alloc] initCount:0
                                     elementSize:sizeof(float)
                                     description:"f"];
    loopBuffer = [[Storage alloc] initCount:0
                                  elementSize:sizeof(int)
                                  description:"i"];
    len = strlen(currentText);
    for (i = 0; i < len; i++) {
	WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i];
	BOOL haveInitial;

	[[outlineStack empty] setNumSlots:0];
	[[loopStack empty] setNumSlots:0];
	nLoops = metrics.numLoops;
	haveInitial = NO;
	for (j = 0; j < nLoops; j++) {
	    float x0, y0, x1, y1, x2, y2;
	    
	    nCntlPts = *(int *)[loops elementAt:loopIdx+j];
	    x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4];
	    y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3];
	    x1 = *(float *)[outline elementAt:outlineIdx];
	    y1 = *(float *)[outline elementAt:outlineIdx+1];
	    x2 = *(float *)[outline elementAt:outlineIdx+2];
	    y2 = *(float *)[outline elementAt:outlineIdx+3];
	    if (IsCounter(x0, y0, x1, y1, x2, y2)) {
		if (!haveInitial) {
		    [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
		    [loopBuffer addElement:(void *)&nCntlPts];
		    if ([outlineStack count] > 0) {
			int count = [outlineStack count];
			
			for (k = 0; k < count; k++) {
			    [self copyControlPoints:*(int *)[loopStack elementAt:k]
			          startingAt:*(int *)[outlineStack elementAt:k]
				  to:outlineBuffer];
			    [loopBuffer addElement:[loopStack elementAt:k]];
			}
			[[outlineStack empty] setNumSlots:0];
			[[loopStack empty] setNumSlots:0];
		    }
		    haveInitial = YES;
		} else {
		    [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
		    [loopBuffer addElement:(void *)&nCntlPts];
		}
	    } else {
		if (!haveInitial) {
		    [outlineStack addElement:(void *)&outlineIdx];
		    [loopStack addElement:(void *)&nCntlPts];
		} else {
		    [self copyControlPoints:nCntlPts startingAt:outlineIdx to:outlineBuffer];
		    [loopBuffer addElement:(void *)&nCntlPts];
		}
	    }
	    outlineIdx += nCntlPts;
	}
	if ([outlineStack count] > 0) {
	    int count = [outlineStack count];
	    
	    for (k = 0; k < count; k++) {
		[self copyControlPoints:*(int *)[loopStack elementAt:k]
			startingAt:*(int *)[outlineStack elementAt:k]
			to:outlineBuffer];
		[loopBuffer addElement:[loopStack elementAt:k]];
	    }
	}
	loopIdx += metrics.numLoops;
    }
    [loopStack free];
    loopStack = nil;
    [outlineStack free];
    outlineStack = nil;
    loops = [loops free];
    outline = [outline free];
    loops = loopBuffer;
    outline = outlineBuffer;
    return self;
}

- (Storage *)glyphBoundaries:(WW3DTextMetrics *)metrics :(int)loopIdx :(int)outlineIdx
{
    Storage *boundaries;
    int i = 0, nLoops, nCntlPts;
    
    boundaries = [[Storage alloc] initCount:0
				  elementSize:sizeof(int)
				  description:"i"];
    nLoops = metrics->numLoops;
    if (!nLoops) return boundaries;
    nCntlPts = *(int *)[loops elementAt:loopIdx];
    [boundaries addElement:(void *)&i];
    outlineIdx += nCntlPts;
    for (i = 1; i < nLoops; i++) {
	int nCntlPts = *(int *)[loops elementAt:loopIdx+i];
	float x0, y0, x1, y1, x2, y2;
	
	x0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-4];
	y0 = *(float *)[outline elementAt:outlineIdx+nCntlPts-3];
	x1 = *(float *)[outline elementAt:outlineIdx];
	y1 = *(float *)[outline elementAt:outlineIdx+1];
	x2 = *(float *)[outline elementAt:outlineIdx+2];
	y2 = *(float *)[outline elementAt:outlineIdx+3];
	if (IsCounter(x0, y0, x1, y1, x2, y2)) {
	    [boundaries addElement:(void *)&i];
	}
	outlineIdx += nCntlPts;
    }
    return boundaries;
}

- (int)boundaryAfter:(int)idx in:(Storage *)boundaries withNumLoops:(int)numLoops
{
    int count = [boundaries count];
    return (idx == count-1) ? numLoops : *(int *)[boundaries elementAt:idx+1];
}

- readCharPath:(const char *)theText usingFont:(Font *)theFont
{
    if (!theText) return self;
    if (currentText) NXZoneFree([self zone], currentText);
    currentText = NXCopyStringBufferFromZone(theText, [self zone]);
    currentFont = theFont;
    [self getCharPath];
    [self orderCounters];
    return self;
}

- generateRIBCommandList
/*
 * Okay, so the idea here is to grovel over each character in the
 * string, placing it, in order, into a RIBCommandList.  After we've
 * generated all the characters, we need to translate it so that it's
 * centered around the origin, and then we need to scale it so that it's
 * some reasonable size.  As a purely ad hoc decision, I'll designate 72
 * pts as 1.0, and work from there.  Once we have the translation and
 * scaling info, we'll malloc up the appropriate RIBTransformBegin,
 * RIBTranslate, RIBScale, and RIBTransformEnd objects to the appropriate
 * places in the RIBCommandList.  Hmm... do we need those to be objects?
 * Think about it this way: when we dump this scene out, do we spit out
 * a set of tokens containing the atomic rib commands that compose this object,
 * or do we spit out some new token corresponding to this WW3DText object?
 * I think the answer is pretty obviously the latter.  Given that, it
 * doesn't even make a whole lot of sense to use a RIBCommandList to hold
 * our polygon info, except for the fact that it makes certain operations
 * easier to understand (bounding boxes, etc.).  There is some performance
 * hit, but let's ignore that for now.
 *
 * well, the reason for a compound command like this to represent itself
 * as a set of objects is so that it can be motion blurred.  By not making
 * the internals of the command opaque, we make it possible for an EveCommand
 * to have a chance of motion blurring this command, the same way it could
 * an atomic RIB command.  Remember, the point of a compound command is
 * so that we can motion blur this command with other versions of itself.
 */
{
    int       i, j, k, len, loopIdx = 0, outlineIdx = 0;
    RtBound   polygonsBoundingBox;
    RtFloat   polygonsCentroid[3], scaleFactor;
    id        transformBeginCommand, transformEndCommand, 
              translateCommand, scaleCommand;
    char *splitText, *curWord;
    
    splitText = NXCopyStringBufferFromZone(currentText, [self zone]);
    curWord = strtok(splitText, " \t\r\n\v\f");

    // I should make sure there is some non-whitespace text before I waste my time...
    [ribCommandList empty];
    len = strlen(currentText);
    for (i = 0; i < len; i++) {
	WW3DTextMetrics metrics = *(WW3DTextMetrics *)[textMetrics elementAt:i];
	RIBArchiveRecord *archiveRecord;
	Storage *boundaries;
	int count;

	if (NXIsSpace(currentText[i])) {
	    curWord = strtok(NULL, " \t\r\n\v\f");
	}
	
	boundaries = [self glyphBoundaries:&metrics :loopIdx :outlineIdx];
	count = [boundaries count];
	
	/*
	 * Provide an RiArchiveRecord describing the character we are about to model:
	 *
	 *	# 'W' (of ``WavesWorld'') in Helvetica 12 point
	 */
	if (curWord && count > 0) {
	    char *comment;
	    int recLen;
	    
	    recLen = strlen(curWord) + strlen([currentFont name]) + PadComment;
	    comment = (char *)NXZoneMalloc([self zone], sizeof(char)*recLen);
	    sprintf(comment, " '%c' (of ``%s'') in %s %d point", currentText[i],
                                                                 curWord,
                                                                 [currentFont name],
                                                                 (int)[currentFont pointSize]);
	    archiveRecord = [[RIBArchiveRecord alloc] init];
	    [archiveRecord setType:RI_COMMENT format:comment];
	    [ribCommandList addObject:archiveRecord];
	    NXZoneFree([self zone], comment);
	}
	
	for (j = 0; j < count; j++) {
	    RIBGeneralPolygon *genP = [[RIBGeneralPolygon alloc] init];
	    RtInt *loopsVertices = NULL, *lvp;
	    RtFloat *vertices = NULL, *vp;
	    RtToken *theTokens;
	    RtPointer *parms;
	    int nLoops, nVertices = 0;
	    int theBoundary, nextBoundary;
	    char **archiveV, archiveInfo[80];
            int  printfTypeV[1], printfNV[1];

	    
	    theBoundary = *(int *)[boundaries elementAt:j];
	    nextBoundary = [self boundaryAfter:j in:boundaries withNumLoops:metrics.numLoops];
	    nLoops = nextBoundary - theBoundary;
	    loopsVertices = (RtInt *)NXZoneMalloc([self zone], nLoops*sizeof(RtInt));
	    if (!loopsVertices) {
		[genP free];
                genP = nil;
		return self;
	    }
	    lvp = loopsVertices;
	    for (k = theBoundary; k < nextBoundary; lvp++, k++) {
		*lvp = (*(int *)[loops elementAt:loopIdx+k])>>1;
		nVertices += *lvp;
	    }
	    vertices = (RtFloat *)NXZoneMalloc([self zone], nVertices*3*sizeof(RtFloat));
	    if (!vertices) {
		[genP free];
                genP = nil;
		NXZoneFree([self zone], loopsVertices);
                loopsVertices = NULL;
		return self;
	    }
	    vp = vertices;
	    for (k = theBoundary; k < nextBoundary; k++) {
		int s, nCntlPts = *(int *)[loops elementAt:loopIdx+k];
		for (s = outlineIdx; s < nCntlPts+outlineIdx; s += 2) {
		    *vp++ = (RtFloat)(*(float *)[outline elementAt:s]);
		    *vp++ = (RtFloat)(*(float *)[outline elementAt:s+1]);
		    *vp++ = (RtFloat)0.0;
		}
		outlineIdx += nCntlPts;
	    }
	    archiveV = (char **)NXZoneMalloc([self zone], sizeof(char *));
	    sprintf(archiveInfo, "[%df]", nVertices*3);
	    *archiveV = NXCopyStringBuffer(archiveInfo);
	    theTokens = (RtToken *)NXZoneMalloc([self zone], sizeof(RtToken));
	    *theTokens = RI_P;
	    parms = (RtPointer *)NXZoneMalloc([self zone], sizeof(RtPointer));
	    *parms = (RtPointer)vertices;
            printfTypeV[0] = WW_POINT;
            printfNV[0] = 3*nVertices;
	    [genP setNLoops:nLoops
		  nVertices:loopsVertices
		  n:1
		  tokens:theTokens
		  parms:parms
		  archiveVector:archiveV
                  printfTypeVector:printfTypeV printfNVector:printfNV];
            [genP setMyShape:myShape];
	    [ribCommandList addObject:genP];
	}
	loopIdx += metrics.numLoops;
	[boundaries free];
        boundaries = nil;
    }

    /*
     * okay, now we have a RIBCommandList that has all the polygons in
     * it.  We need to get it's bounding box and calculate how we should
     * translate it appropriately to (left, right, center) justify it.
     */
    if (![ribCommandList hasBoundingBox])
    {  // see if our text is just spaces.  If it's not, this is a bug...
       if (currentText)  // okay, we're pointing at something
       {  int  i = 0, howMany = strlen(currentText);

          while (isspace(currentText[i]) && i < howMany) { i++; }
          if (i != howMany)
          {  NXLogError("WW3DText: although my currentText is not just spaces (it's <%s>), my polygons have no boundingBox - bailing...");
             [self free];
             return nil;
 	  }
          else
	  {  return self;  // we're blank; we're done
 	  }
       }
       // okay, we've just got no text in us. (there's a song here somewhere...)
       polygonsBoundingBox[0] = 0;
       polygonsBoundingBox[1] = 0;
       polygonsBoundingBox[2] = 0;
       polygonsBoundingBox[3] = 0;
       polygonsBoundingBox[4] = 0;
       polygonsBoundingBox[5] = 0;
    }
    else
    {  N3D_CopyBound(*([ribCommandList boundingBoxStartingAt:0 endingAt:0]), polygonsBoundingBox);
    }
    if (justification == NX_LEFTALIGNED)
    {  polygonsCentroid[0] = 0;
       polygonsCentroid[1] = 0;
       polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
    }
    if (justification == NX_CENTERED)
    {  polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0])/2.0)); 
       polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2])/2.0)); 
       polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
    }
    if (justification == NX_RIGHTALIGNED)
    {  polygonsCentroid[0] = -1 * (polygonsBoundingBox[0] + ((polygonsBoundingBox[1] - polygonsBoundingBox[0])));
       polygonsCentroid[1] = -1 * (polygonsBoundingBox[2] + ((polygonsBoundingBox[3] - polygonsBoundingBox[2]))); 
       polygonsCentroid[2] = -1 * (polygonsBoundingBox[4] + ((polygonsBoundingBox[5] - polygonsBoundingBox[4])/2.0)); 
    }
    // we now want to build up a RIBTranslate object which will move that center to (0,0,0)
    translateCommand = [[RIBTranslate alloc] init];
    [translateCommand setDX:polygonsCentroid[0] dy:polygonsCentroid[1] dz:polygonsCentroid[2]];
    [translateCommand setMyShape:myShape];

    // now we need to find out how big the font we're using is, so we can scale ourselves appropriately
    // we want 72 pts to equal 1 in Y.
    if (polygonsBoundingBox[3] != polygonsBoundingBox[2])
    {  scaleFactor = ([currentFont pointSize]/72.0) * (1./(polygonsBoundingBox[3] - polygonsBoundingBox[2]));
    }
    else
    {  scaleFactor = 1;
    }
    scaleCommand = [[RIBScale alloc] init];
    [scaleCommand setSX:scaleFactor sy:scaleFactor sz:scaleFactor];
    [scaleCommand setMyShape:myShape];

    transformBeginCommand = [[[RIBTransformBegin alloc] init] setMyShape:myShape];
    transformEndCommand = [[[RIBTransformEnd alloc] init] setMyShape:myShape];

    // okay, now let's insert the various RIBCommands in the right place in the list.
    // we want: {RIBTransformBegin, RIBScale, RIBTranslate, ... RIBGeneralPolygon ... RIBTransformEnd}
    // "wait a minute!", I hear you saying.  "Don't we need to translate before we do the scale?" 
    // Yes, but remember that RenderMan does things backwards, so this is actually correct...
    [ribCommandList addObject:transformEndCommand];
    [ribCommandList insertObject:translateCommand at:0];
    [ribCommandList insertObject:scaleCommand at:0];
    [ribCommandList insertObject:transformBeginCommand  at:0];

    /*
     * Remembering to release the splitText.
     */
    NXZoneFree([self zone], splitText);
    return self;
}

@end

@implementation WW3DText
static const char *InvalidJustification = "%s: %d is an invalid value for justification: only NX_LEFTALIGNED, NX_RIGHTALIGNED, NX_CENTERED are valid.\n";

+ initialize { return [WW3DText setVersion:2], self; }

- init
{
    [super init];
    currentText = NULL;
    outline = [[Storage alloc] initCount:0
                               elementSize:sizeof(float)
                               description:"f"];
    textMetrics = [[Storage alloc] initCount:0
                                   elementSize:sizeof(WW3DTextMetrics)
			           description:"{iii}"];
    loops = [[Storage alloc] initCount:0
                             elementSize:sizeof(int)
                             description:"i"];
    ribCommandList  = [[RIBCommandList alloc] init];
    justification = NX_CENTERED;
    dirtyBoundingBox = YES;
    return self;
}

- awake
{
    [super awake];
    dirtyBoundingBox = YES;
    return self;
}

- initWithCharPath:(const char *)theText
    usingFont:(Font *)theFont
    myShape:newMyShape
    justification:(int)newJustification
/*
 * This is the designated initializer for WW3DText. It returns a WW3DText
 * object which conforms to the WWRenderable protocol.
 */
{
    [self init];
    if ((newJustification != NX_LEFTALIGNED) &&
	(newJustification != NX_RIGHTALIGNED) &&
	(newJustification != NX_CENTERED)) {
	NXLogError(InvalidJustification, [WW3DText name], newJustification);
    } else {
	justification = newJustification;
    }
    myShape = newMyShape;
    [self readCharPath:theText usingFont:theFont];
    [self generateRIBCommandList];
    return self;
}


- setCharPath:(const char *)theText usingFont:(Font *)theFont myShape:newMyShape justification:(int)newJustification
{
    // in case we pop out, wannt zero these guys out ASAP...
    currentText = NULL;
    outline = nil;
    textMetrics = nil;
    loops = nil;


    outline = [[Storage alloc] initCount:0
                               elementSize:sizeof(float)
                               description:"f"];
    textMetrics = [[Storage alloc] initCount:0
                                   elementSize:sizeof(WW3DTextMetrics)
			           description:"{iii}"];
    loops = [[Storage alloc] initCount:0
                             elementSize:sizeof(int)
                             description:"i"];
    dirtyBoundingBox = YES;

    if ((newJustification != NX_LEFTALIGNED) &&
	(newJustification != NX_RIGHTALIGNED) &&
	(newJustification != NX_CENTERED)) {
	NXLogError(InvalidJustification, [WW3DText name], newJustification);
    } else {
	justification = newJustification;
    }
    myShape = newMyShape;
    // we don't want to wipe out the old version...
    if (theText)
    {  currentText = NXCopyStringBufferFromZone(theText, [self zone]);
    }
    else
    {  currentText = NULL;
    }
    [self readCharPath:theText usingFont:theFont];
    [self generateRIBCommandList];
    return self;
}

- free
{
    if (currentText) NX_FREE(currentText);
    [outline free];
    [textMetrics free];
    [loops free];
    //NXLogError("WW3DText %p : freeing list %p", self, ribCommandList);
    [ribCommandList free];
    return [super free];
}

- _setRIBCommandList:newRIBCommandList 
{
   ribCommandList = newRIBCommandList; 
   //NXLogError("set WW3DText %p's ribCommandList to %p", self, ribCommandList); 
   return self; 
}

- _setOutline:newOutline textMetrics:newTextMetrics loops:newLoops
{
   outline = newOutline;
   textMetrics = newTextMetrics;
   loops = newLoops;

   return self;
}

- _setCurrentText:(const char *)newText
{
   currentText = NXCopyStringBuffer(newText);

   return self;
}

- ribCommandList { return ribCommandList; }


- copyFromZone:(NXZone *)zone
{
   id   newCopy = [super copyFromZone:zone];


   [newCopy _setRIBCommandList:[ribCommandList copyFromZone:zone]];
   [newCopy _setOutline:[outline copyFromZone:zone] textMetrics:[textMetrics copyFromZone:zone] loops:[loops copyFromZone:zone]];
   [newCopy _setCurrentText:currentText];

   return newCopy;
}


// for right now, we're not going to support lerping.  The only useful
// lerping would be with the same text changing size, anyway...
- lerpWith:b by:(float)uValue
{
   if (([self class] != [b class]) || (uValue <= 0.0))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }
   return self;
}

- lerpSelfWith:b by:(float)uValue
{
   if (([self class] != [b class]) || (uValue <= 0.0))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   return self;
}

- (BOOL)isLerpable { return YES; }

- (BOOL)hasBoundingBox		{ return YES; }

// there is a bug in prman 3.4 that makes it muck up doing motion blur
// on GeneralPolygons.  Note that rendrib doesn't have this bug, and
// renders GeneralPolygons in a MotionBegin/End just fine.  Given that
// most people are using prman 3.4, I'm going to have WW3DText say that
// it's not motion blurrable for now...
- (BOOL)isMotionBlurrable	{ return YES; }
- (BOOL)isCompoundCommand	{ return YES; }

- setBoundingBox:(RtBound *)newBoundingBox
{
    N3D_CopyBound(*newBoundingBox, boundingBox);
    return self;
}

- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  
{  
    dirtyBoundingBox = NO;
    return [self setBoundingBox:[ribCommandList boundingBoxStartingAt:shutterOpenTime endingAt:shutterCloseTime]];
}

- (RtBound *)boundingBoxStartingAt:(RtFloat)intervalStartTime endingAt:(RtFloat)intervalEndTime
{ 
   if (dirtyBoundingBox) 
   {  [self calculateBoundingBoxStartingAt:intervalStartTime endingAt:intervalEndTime];
   }
   return &boundingBox; 
}


- (float)lastSampleIsAt { return 0.0; }

- (unsigned long int)maxSampleBandwidth { return [ribCommandList maxSampleBandwidth]; }

- setMyShape:shape
{
    myShape = shape;
    return self;
}

- shape
{
    return myShape;
}

- renderMaps:(WW3DCamera *)camera
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
    return self;
}

- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
    return self;
}

- renderMaps:(WW3DCamera *)camera
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime
{
    return self;
}

- renderMaps:(WW3DCamera *)camera
{
    return self;
}

- renderSelfAsBox:(WW3DCamera *)camera
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime
{
    return [ribCommandList renderSelfAsBox:camera
			   startingAt:shutterOpenTime
			   endingAt:shutterCloseTime];
}

- renderSelf:(WW3DCamera *)camera
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime
{
    return [ribCommandList renderSelf:camera
			   startingAt:shutterOpenTime
			   endingAt:shutterCloseTime];
}

- renderSelf:(WW3DCamera *)camera
{
    return [ribCommandList renderSelf:camera];
}

- preRenderSelf:(WW3DCamera *)camera
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime
{
    return [ribCommandList preRenderSelf:camera
			   startingAt:shutterOpenTime
			   endingAt:shutterCloseTime];
}

- preRenderSelf:(WW3DCamera *)camera
{
    return [ribCommandList preRenderSelf:camera];
}

- transformCTM:(WW3DAttributeState *)attributeState
    startingAt:(RtFloat)shutterOpenTime
    endingAt:(RtFloat)shutterCloseTime 
{
    return [ribCommandList transformCTM:attributeState
                           startingAt:shutterOpenTime
			   endingAt:shutterCloseTime]; 
}


// methods to make me look like a compound command:
- (int)count {  return [ribCommandList count]; }
- objectAt:(int)i {  return [ribCommandList objectAt:i]; }


// WavesWorld archiving:
// writeEve:(NXStream *)stream
// writeScene:(NXStream *)stream

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;
    
   for (i = 0; i < tab; i++) 
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "WW3DText {%s} %f {%s} ", [currentFont name], [currentFont pointSize], currentText); 
   if (justification == NX_LEFTALIGNED)
   {  NXPrintf(stream, "left;");
   } 
   else
   {  if (justification == NX_CENTERED)
      {  NXPrintf(stream, "center;");
      }
      else
      {  if (justification == NX_RIGHTALIGNED)
         {  NXPrintf(stream, "right;");
         }
         else
         {  NXPrintf(stream, ";");
         }
      }
   }

   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   return [self writeEve:stream atTabLevel:tab];
}

- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   NXPrintf(stream, "startShape %s; ", [[self class] name]);
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
   [self writeEve:stream atTabLevel:tab];
   NXPrintf(stream, "};}\n");
  NXPrintf(stream, "endShape;\n");

  return self;
}

- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# ");
   [self writeEve:stream atTabLevel:tab];
   NXPrintf(stream, "\n");

   return [ribCommandList writeInventorAtTime:currentTime to:stream atTabLevel:tab];
}

#define typeVector "@*@@@@"
#define typeValues &currentFont, &currentText, &ribCommandList, &outline, &textMetrics, &loops

- read:(NXTypedStream *)stream 
{
    int version;
    
    [super read:stream];
    version = NXTypedStreamClassVersion(stream,"WW3DText");
    if (version == 0) NXReadTypes(stream, "i", &version), version = 1;
    if (version == 1)
    {  NXReadTypes(stream, typeVector, typeValues);
       NXReadArray(stream, "f", 6, boundingBox);
       myShape = NXReadObject(stream);
       justification = NX_CENTERED;
    }
    if (version == 2) 
    {  NXReadTypes(stream, typeVector, typeValues);
       NXReadArray(stream, "f", 6, boundingBox);
       myShape = NXReadObject(stream);
       NXReadTypes(stream, "i", &justification);
    }

    return self;
}

- write:(NXTypedStream *)stream 
{
    [super write:stream];
    NXWriteTypes(stream, typeVector, typeValues);
    NXWriteArray(stream, "f", 6, boundingBox);
    NXWriteObjectReference(stream, myShape);
    NXWriteTypes(stream, "i", &justification);
    return self;
}

- (char *)currentText { return currentText; }
- currentFont { return currentFont; }

- (BOOL)theSameAs:otherRIBCommand
{
  if (strcmp(currentText, [otherRIBCommand currentText]))
  {  return NO;
  }
  if ([currentFont pointSize] != [[otherRIBCommand currentFont] pointSize])
  {  return NO;
  }
  if (strcmp([currentFont name], [[otherRIBCommand currentFont] name]))
  {  return NO;
  }

  return YES;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}


- (BOOL)isMoot
{
   int  i = 0, howMany = strlen(currentText);


   while (isspace(currentText[i]) && i < howMany) { i++; }
   if (i != howMany)  // okay, I've got some text
   {  return NO;
   }

   return YES;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }


- (BOOL)pushesOrPopsCTM { return NO; }
- (BOOL)pushesCTM { return NO; }
- (BOOL)popsCTM { return NO; }


// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }
@end
